<?php
namespace pfp\lib {
    
    \defined('_JEXEC') or die('Restricted access');

    /**
     * Description of DatabaseAdapter
     * 
     * @todo 
     * 
     * @author George Zeakis<zeageorge@google.com>
     * 
     */
    class DatabaseAdapter implements DatabaseAdapterInterface{

        /**
         * @var PDO
         */
        protected $_pdo = \NULL;

        /**
         *
         * @var array
         */
        protected $_events = array();

        /**
         *
         * @var array
         */
        protected $_vars = array();

        /**
         * @return DatabaseAdapterInterface
         */
        public function __construct() {
            $this->registerEvents();
        }

        private function registerEvents(){
            $this->_events = array(
                'event_onError'                     =>'event_onError',
                'event_onBefore_Connect_to_Database'=>'event_onBefore_Connect_to_Database',
                'event_onAfter_Connect_to_Database' =>'event_onAfter_Connect_to_Database',
                'event_onBefore_Query_Select'       =>'event_onBefore_Query_Select',
                'event_onAfter_Query_Select'        =>'event_onAfter_Query_Select',
                'event_onBefore_Query_Insert'       =>'event_onBefore_Query_Insert',
                'event_onAfter_Query_Insert'        =>'event_onAfter_Query_Insert',
                'event_onBefore_Query_Update'       =>'event_onBefore_Query_Update',
                'event_onAfter_Query_Update'        =>'event_onAfter_Query_Update',
                'event_onBefore_Query_Delete'       =>'event_onBefore_Query_Delete',
                'event_onAfter_Query_Delete'        =>'event_onAfter_Query_Delete',
                'event_onBefore_Query_DeleteAll'    =>'event_onBefore_Query_DeleteAll',
                'event_onAfter_Query_DeleteAll'     =>'event_onAfter_Query_DeleteAll',
            );
            eventManager::registerEvents($this->_events);
        }

        /**
         * 
         * @todo 
         * 
         * @param deepDynamicContainer $dbData 
         * @return boolean Returns TRUE on success or throws an codeExecution_FlowControlException(array('return'=>FALSE)) on error
         */
        public function connect(deepDynamicContainer $dbData){
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }
            try {
                $this->_vars['dbData'] = $dbData;

                if (!\is_null($this->_pdo)){ $this->disconnect(); }

                $eventData = array('event_name'=>$this->_events['event_onBefore_Connect_to_Database'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);

                $this->_pdo = new \PDO($this->_vars['dbData']->type() . ':host=' . $this->_vars['dbData']->host() . ';dbname=' . $this->_vars['dbData']->name(),
                                      $this->_vars['dbData']->username(), $this->_vars['dbData']->user_password(), array(\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));

                $this->_pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
                $this->_pdo->exec("SET CHARACTER SET utf8");

                $eventData = array('event_name'=>$this->_events['event_onAfter_Connect_to_Database'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
            } catch (\Exception $exc) {
                $this->_vars["$tmp"]["errors"] = $exc;
                $eventData = array('event_name'=>$this->_events['event_onError'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
                unset($this->_vars["$tmp"]); 
                throw new codeExecution_FlowControlException(array('return'=>\FALSE), $exc->getMessage());
            }
            return \TRUE;
        }

        /**
         * 
         */
        public function disconnect(){
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }

            $this->_pdo = \NULL;
        }

        /**
         * 
         * @return deepDynamicContainer
         */
        public function get_dbData(){ 
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }

            return $this->_vars['dbData']; 
        }

        /**
         * Quote a string that is used as an identifier
         * (table names, column names etc). This method can
         * also deal with dot-separated identifiers eg table.column
         */
        protected function _quote_identifier($identifier) {
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }

            $parts = \explode('.', $identifier);
            $parts = \array_map(array($this, '_quote_identifier_part'), $parts);
            return \join('.', $parts);
        }

        /**
         * This method performs the actual quoting of a single
         * part of an identifier, using the identifier quote
         * character specified in the config (or autodetected).
         */
        protected function _quote_identifier_part($part) {
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }

            if ($part === '*') {
                return $part;
            }
            $quote_character = '`';
            return $quote_character . $part . $quote_character;
        }

        /**
         * select
         * @param string $sqlQuery An SQL query string
         * @param array $BindParams Paramters to bind
         * @param constant $fetchMode A PDO Fetch mode
         * @return array Returns an assoc array or throws an codeExecution_FlowControlException(array('return'=>FALSE)) on error
         */
        public function select($sqlQuery, array $BindParams = array(), $fetchMode = \PDO::FETCH_ASSOC){
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }
            try {
                $this->_vars["$tmp"]["sqlQuery"]   = $sqlQuery;
                $this->_vars["$tmp"]["bindParams"] = $BindParams;
                $this->_vars["$tmp"]["fetchMode"]  = $fetchMode;

                $eventData = array('event_name'=>$this->_events['event_onBefore_Query_Select'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);

                $sth = $this->_pdo->prepare($this->_vars["$tmp"]["sqlQuery"]);
                foreach ($this->_vars["$tmp"]["bindParams"] as $key => $value) { $sth->bindValue("$key", $value); }

                $this->_vars["$tmp"]["selectResults"]     = $sth->execute();
                $this->_vars["$tmp"]["selectResultsData"] = $sth->fetchAll($this->_vars["$tmp"]["fetchMode"]);

                $eventData = array('event_name'=>$this->_events['event_onAfter_Query_Select'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
            } catch (\Exception $exc) {
                $this->_vars["$tmp"]["errors"] = $exc;
                $eventData = array('event_name'=>$this->_events['event_onError'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
                unset($this->_vars["$tmp"]); 
                throw new codeExecution_FlowControlException(array('return'=>\FALSE), $exc->getMessage());
            }
            $re = $this->_vars["$tmp"]["selectResultsData"]; unset($this->_vars["$tmp"]);
            return $re;
        }

        /**
         * insert
         * @param string $table A name of table to insert into
         * @param string $data An associative array
         * @return integer Returns the id of the inserted row or throws an codeExecution_FlowControlException(array('return'=>FALSE)) on error
         */
        public function insert($table, array $data) {
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }
            try{
                $this->_vars["$tmp"]["tableName"]    = $table;
                $this->_vars["$tmp"]["dataToInsert"] = $data;

                \ksort($this->_vars["$tmp"]["dataToInsert"]);
                $fieldNames = \implode('`, `', \array_keys($this->_vars["$tmp"]["dataToInsert"]));
                $fieldValues = ':' . \implode(', :', \array_keys($this->_vars["$tmp"]["dataToInsert"]));

                $this->_vars["$tmp"]["sqlQuery"] = "INSERT INTO `".$this->_vars["$tmp"]["tableName"]."` (`$fieldNames`) VALUES ($fieldValues)";

                $eventData = array('event_name'=>$this->_events['event_onBefore_Query_Insert'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);

                $sth = $this->_pdo->prepare($this->_vars["$tmp"]["sqlQuery"]);
                foreach ($this->_vars["$tmp"]["dataToInsert"] as $key => $value) { $sth->bindValue(":$key", $value); }

                $this->_vars["$tmp"]["insertResults"] = $sth->execute();
                $this->_vars["$tmp"]["lastInsertID"]  = $this->_pdo->lastInsertId();

                $eventData = array('event_name'=>$this->_events['event_onAfter_Query_Insert'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
            } catch (\Exception $exc){
                $this->_vars["$tmp"]["errors"] = $exc;
                $eventData = array('event_name'=>$this->_events['event_onError'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
                unset($this->_vars["$tmp"]); 
                throw new codeExecution_FlowControlException(array('return'=>\FALSE), $exc->getMessage());
            }
            $re = $this->_vars["$tmp"]["lastInsertID"]; unset($this->_vars["$tmp"]);
            return $re;
        }

        /**
         * update
         * @param string $table A name of table to insert into
         * @param array $data An associative array
         * @param string $where the WHERE query part
         * @return integer Returns the number of affected rows or throws an codeExecution_FlowControlException(array('return'=>FALSE)) on error
         */
        public function update($table, array $data, $where) {
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }
            try{
                $this->_vars["$tmp"]["tableName"]    = $table;
                $this->_vars["$tmp"]["dataToUpdate"] = $data;
                $this->_vars["$tmp"]["where"]        = $where;

                \ksort($this->_vars["$tmp"]["dataToUpdate"]);
                $fieldDetails = "";
                foreach ($this->_vars["$tmp"]["dataToUpdate"] as $key => $value) { $fieldDetails .= "`$key`=:$key,"; }
                $fieldDetails = \rtrim($fieldDetails, ',');

                $this->_vars["$tmp"]["sqlQuery"] = "UPDATE `".$this->_vars["$tmp"]["tableName"]."` SET $fieldDetails WHERE ".$this->_vars["$tmp"]["where"];

                $eventData = array('event_name'=>$this->_events['event_onBefore_Query_Update'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);

                $sth = $this->_pdo->prepare($this->_vars["$tmp"]["sqlQuery"]);
                foreach ($this->_vars["$tmp"]["dataToUpdate"] as $key => $value) { $sth->bindValue(":$key", $value); }

                $this->_vars["$tmp"]["updateResults"] = $sth->execute();

                $eventData = array('event_name'=>$this->_events['event_onAfter_Query_Update'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
            } catch (\Exception $exc){
                $this->_vars["$tmp"]["errors"] = $exc;
                $eventData = array('event_name'=>$this->_events['event_onError'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
                unset($this->_vars["$tmp"]); 
                throw new codeExecution_FlowControlException(array('return'=>\FALSE), $exc->getMessage());
            }
            $re = $this->_vars["$tmp"]["updateResults"]; unset($this->_vars["$tmp"]);
            return $re;
        }

        /**
         * delete
         * 
         * @param string $table
         * @param string $where
         * @param integer $limit
         * @return integer the number of affected rows or throws an codeExecution_FlowControlException(array('return'=>FALSE)) on error
         */
        public function delete($table, $where, $limit = 1) {
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }
            try{
                $this->_vars["$tmp"]["tableName"] = $table;
                $this->_vars["$tmp"]["where"]     = $where;
                $this->_vars["$tmp"]["limit"]     = $limit;
                $this->_vars["$tmp"]["sqlQuery"]  = "DELETE FROM `".$this->_vars["$tmp"]["tableName"]."` WHERE ".$this->_vars["$tmp"]["where"]." LIMIT ".$this->_vars["$tmp"]["limit"];

                $eventData = array('event_name'=>$this->_events['event_onBefore_Query_Delete'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);

                $this->_vars["$tmp"]["deleteResults"] = $this->_pdo->exec($this->_vars["$tmp"]["sqlQuery"]);

                $eventData = array('event_name'=>$this->_events['event_onAfter_Query_Delete'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
            } catch (\Exception $exc){
                $this->_vars["$tmp"]["errors"] = $exc;
                $eventData = array('event_name'=>$this->_events['event_onError'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
                unset($this->_vars["$tmp"]); 
                throw new codeExecution_FlowControlException(array('return'=>\FALSE), $exc->getMessage());
            }
            $re = $this->_vars["$tmp"]["deleteResults"]; unset($this->_vars["$tmp"]);
            return $re;
        }

        /**
         * delete_all
         * @param string $table
         * @param string $where
         * @return integer the number of affected rows or throws an codeExecution_FlowControlException(array('return'=>FALSE)) on error
         */
        public function delete_all($table, $where) {
            $tmp = __FUNCTION__."_tmp"; if (isset($this->_vars["$tmp"])){ unset($this->_vars["$tmp"]); }
            try{
                $this->_vars["$tmp"]["tableName"] = $table;
                $this->_vars["$tmp"]["where"]     = $where;
                $this->_vars["$tmp"]["sqlQuery"]  = "DELETE FROM `".$this->_vars["$tmp"]["tableName"]."` WHERE ".$this->_vars["$tmp"]["where"];

                $eventData = array('event_name'=>$this->_events['event_onBefore_Query_DeleteAll'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);

                $this->_vars["$tmp"]["deleteAll_Results"] = $this->_pdo->exec($this->_vars["$tmp"]["sqlQuery"]);

                $eventData = array('event_name'=>$this->_events['event_onAfter_Query_DeleteAll'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
            } catch (\Exception $exc){
                $this->_vars["$tmp"]["errors"] = $exc;
                $eventData = array('event_name'=>$this->_events['event_onError'],'args'=>&$this->_vars); eventManager::riseEvent($eventData);
                unset($this->_vars["$tmp"]); 
                throw new codeExecution_FlowControlException(array('return'=>\FALSE), $exc->getMessage());
            }
            $re = $this->_vars["$tmp"]["deleteAll_Results"]; unset($this->_vars["$tmp"]);
            return $re;
        }

    }
}